有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

如何使用Gson将JSON映射转换为自定义Java列表?

给出这样的JSON映射:

{
    "category1": [],
    "category2": ["item1"],
    "category3": ["item1", "item2"]
}

我想将其转换为类别的Java ArrayList(而不是Map),如下所示:

class Category {
  final String categoryName;
  final ArrayList<String> items;

  public Category(String categoryName, ArrayList<String> items) {
    this.categoryName = categoryName;
    this.items = items;
  }
}

ArrayList<Category> data = new ArrayList<>();

所以我希望data看起来像这样:

for (Category c: data) {
  System.out.println(c.categoryName + ", " + c.items);
}
/**
Expected data =
category1, empty list [] or null
category2, [item1]
category2, [item1, item2]
**/

我试过使用Gson库:

应用/构建。格拉德尔:

dependencies {
   ...
   implementation 'com.google.code.gson:gson:2.8.6'
}

JSONConvertest。java

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonParser;
import com.google.gson.internal.LinkedTreeMap;
import com.google.gson.reflect.TypeToken;

import org.junit.Test;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

public class JsonConverterTest {

  @Test
  public void gsonMapToListTest() {
    String json = "{\"category1\": [],\"category2\": [\"item1\"],\"category3\": [\"item1\", \"item2\"]}";

    class Category {
      final String categoryName;
      final ArrayList<String> items;

      public Category(String categoryName, ArrayList<String> items) {
        this.categoryName = categoryName;
        this.items = items;
      }
    }

    ArrayList<Category> expectedData = new ArrayList<>();
    expectedData.add(new Category("category1", new ArrayList<String>()));
    expectedData.add(new Category("category2", new ArrayList<>(Arrays.asList("item1"))));
    expectedData.add(new Category("category2", new ArrayList<>(Arrays.asList("item1", "item2"))));

    System.out.println("Expected Data:");
    for (Category c: expectedData) {
      System.out.println(c.categoryName + ", " + c.items);
    }

    // This works, but it returns a Map instead of a List
    LinkedTreeMap<String, ArrayList<String>> dataMap = new Gson().fromJson(json, LinkedTreeMap.class);
    System.out.println("\n data as Map = " + dataMap);

    // This causes "IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $"
    Type listOfCategoriesType = new TypeToken<ArrayList<Category>>() {}.getType();
    ArrayList<Category> data = new Gson().fromJson(json, listOfCategoriesType); // IllegalStateException here
    assertThat(data, is(expectedData));

    // This causes "java.lang.IllegalStateException: Not a JSON Array: {...}"
    JsonArray jsonArray = JsonParser.parseString(json).getAsJsonArray(); // IllegalStateException here
    data = new Gson().fromJson(jsonArray, listOfCategoriesType);
    assertThat(data, is(expectedData));
  }
}

使用指南https://www.baeldung.com/gson-listhttps://futurestud.io/tutorials/gson-mapping-of-maps,我只能将JSON映射转换为Java映射。但如果我试图将JSON映射转换为Java列表,就会得到一个非法状态异常:

Type listOfCategoriesType = new TypeToken<ArrayList<Category>>() {}.getType();
ArrayList<Category> data = new Gson().fromJson(json, listOfCategoriesType); // IllegalStateException

或者

JsonArray jsonArray = JsonParser.parseString(json).getAsJsonArray(); // IllegalStateException
ArrayList<Category> data2 = new Gson().fromJson(jsonArray, listOfCategoriesType);

那么,在Gson中,根据上面的单元测试gsonMapToListTest(),将Json映射转换为Java列表的正确方法是什么呢


共 (2) 个答案

  1. # 1 楼答案

    这不是最简单的方法,但Gson允许实现这样的类型适配器,这样您就可以在结果对象中反序列化JSON,而无需中间集合

    public final class CombinerTypeAdapterFactory<V, C>
            implements TypeAdapterFactory {
    
        // used to create a collection to populate
        private final TypeToken<? extends Collection<? super C>> collectionTypeToken;
        // represents a type of each map entry value
        private final TypeToken<V> valueTypeToken;
        // a strategy to map an entry to an element
        private final BiFunction<? super String, ? super V, ? extends C> combine;
    
        private CombinerTypeAdapterFactory(final TypeToken<? extends Collection<? super C>> collectionTypeToken, final TypeToken<V> valueTypeToken,
                final BiFunction<? super String, ? super V, ? extends C> combine) {
            this.collectionTypeToken = collectionTypeToken;
            this.valueTypeToken = valueTypeToken;
            this.combine = combine;
        }
    
        public static <V, C> TypeAdapterFactory create(final TypeToken<List<C>> collectionTypeToken, final TypeToken<V> valueTypeToken,
                final BiFunction<? super String, ? super V, ? extends C> combine) {
            return new CombinerTypeAdapterFactory<>(collectionTypeToken, valueTypeToken, combine);
        }
    
        @Override
        @Nullable
        public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
            // check if supported
            if ( !typeToken.equals(collectionTypeToken) ) {
                return null;
            }
            // grab the value type adapter
            final TypeAdapter<V> valueTypeAdapter = gson.getAdapter(valueTypeToken);
            // and the type adapter the collection to be populated
            final TypeAdapter<? extends Collection<? super C>> collectionTypeAdapter = gson.getDelegateAdapter(this, collectionTypeToken);
            return new TypeAdapter<T>() {
                @Override
                public void write(final JsonWriter out, final T value) {
                    throw new UnsupportedOperationException(); // TODO
                }
    
                @Override
                public T read(final JsonReader in)
                        throws IOException {
                    // assuming all of those are always { e1, e2, e3, ... }
                    in.beginObject();
                    // a hack to create an empty collection (it's modifiable right?)
                    final Collection<? super C> collection = collectionTypeAdapter.fromJson("[]");
                    // for each entry in the map that's being read
                    while ( in.hasNext() ) {
                        // get is name
                        final String name = in.nextName();
                        // and its value
                        final V value = valueTypeAdapter.read(in);
                        // combine
                        final C combined = combine.apply(name, value);
                        // and add to the list
                        collection.add(combined);
                    }
                    in.endObject();
                    // we know better than javac
                    @SuppressWarnings("unchecked")
                    final T result = (T) collection;
                    return result;
                }
            }
                    .nullSafe();
        }
    
    }
    

    以下测试为绿色:

    public final class CombinerTypeAdapterFactoryTest {
    
        @AllArgsConstructor
        @EqualsAndHashCode
        @ToString
        private static final class Category {
    
            @Nullable
            final String categoryName;
    
            @Nullable
            final List<String> items;
    
        }
    
        private static final Gson gson = new GsonBuilder()
                .disableInnerClassSerialization()
                .disableHtmlEscaping()
                .registerTypeAdapterFactory(CombinerTypeAdapterFactory.create(new TypeToken<List<Category>>() {}, new TypeToken<List<String>>() {}, Category::new))
                .create();
    
        @Test
        public void test()
                throws IOException {
            try ( final JsonReader jsonReader = new JsonReader(new InputStreamReader(CombinerTypeAdapterFactoryTest.class.getResourceAsStream("input.json"))) ) {
                final List<Category> actualCategories = gson.fromJson(jsonReader, new TypeToken<List<Category>>() {}.getType());
                Assertions.assertIterableEquals(
                        ImmutableList.of(
                                new Category("category1", ImmutableList.of()),
                                new Category("category2", ImmutableList.of("item1")),
                                new Category("category3", ImmutableList.of("item1", "item2"))
                        ),
                        actualCategories
                );
            }
        }
    
    }
    
  2. # 2 楼答案

    最简单的方法是简单地解析成Map,然后将Map转换成List<Category>

    LinkedTreeMap<String, ArrayList<String>> dataMap = new Gson().fromJson(json,
            new TypeToken<LinkedTreeMap<String, ArrayList<String>>>() {}.getType());
    
    ArrayList<Category> dataList = new ArrayList<>();
    for (Entry<String, ArrayList<String>> entry : dataMap.entrySet())
        dataList.add(new Category(entry.getKey(), entry.getValue()));
    

    另一种方法是编写自己的^{},或者使用另一个JSON库